use std::{
    f32::consts::PI,
    ops::Neg,
    time::{Duration, SystemTime},
};

use bevy::prelude::*;

use crate::{data::*, GameArg, GameRng};

pub struct UpdatePlugin;

impl Plugin for UpdatePlugin {
    fn build(&self, app: &mut App) {
        // Sync Part
        app.add_systems(PreUpdate, update_tips);
        app.add_systems(
            PreUpdate,
            (ctrl_keyboard_ready, ctrl_keyboard_playing, ctrl_process_end),
        );
        app.add_systems(Update, camera_tracking);
        // Fixed Part
        app.add_systems(FixedUpdate, on_status_changed);
        app.add_systems(
            FixedUpdate,
            (ctrl_child_process_ready, ctrl_child_process_playing).after(on_status_changed),
        )
        .add_systems(
            FixedUpdate,
            (
                move_horizontal.after(ctrl_child_process_playing),
                move_ball_caught.after(move_horizontal),
            ),
        )
        .add_systems(
            FixedUpdate,
            (
                catch_ball.after(move_horizontal),
                ball_out.after(move_horizontal),
                check_restricted_area.after(move_horizontal),
            ),
        );
        // CPS Test
        // app.insert_resource(PerfCount::default());
        // app.add_systems(FixedUpdate, update_count_system);
    }
}

/*
 * 状态转换
 */
#[allow(clippy::type_complexity, clippy::too_many_arguments)]
fn on_status_changed(
    mut commands: Commands,
    mut meshes: ResMut<Assets<Mesh>>,
    mut materials: ResMut<Assets<StandardMaterial>>,
    query_status: Query<(Entity, &GameStatus), With<GameStatusChanged>>,
    mut query_robot: Query<
        (&mut Transform, &mut HorizontalMovingStatus),
        (With<Robot>, Without<Ball>),
    >,
    mut query_ball: Query<
        (&mut Transform, &mut HorizontalMovingStatus),
        (With<Ball>, Without<Robot>),
    >,
    query_restricted_area: Query<Entity, With<RestrictedArea>>,
    field_data: Res<FieldData>,
    mut statistics: ResMut<Statistics>,
    game_arg: Res<GameArg>,
    mut game_rng: ResMut<GameRng>,
) {
    // 获取当前状态，除去标记
    let Ok((entity, game_status)) = query_status.get_single() else {
        return;
    };
    commands.entity(entity).remove::<GameStatusChanged>();

    let (mut robot_tran, mut robot_mov) = query_robot.single_mut();
    let (mut ball_tran, mut ball_mov) = query_ball.single_mut();
    let FieldData {
        field_length,
        field_width,
        goal_area_width,
        ..
    } = field_data.to_owned();
    // 根据当前状态执行相应操作
    match game_status {
        GameStatus::Ready { .. } => {
            // 设置并锁定位置
            *robot_tran = Transform::from_xyz(-field_length / 2., ROBOT_HEIGHT / 2., 0.0)
                .looking_at(Vec3::new(0.0, ROBOT_HEIGHT / 2., 0.0), Vec3::Y);
            *ball_tran = Transform::from_xyz(-field_length / 2., BALL_RADIUS, field_width / 2.);
            *robot_mov = HorizontalMovingStatus::default();
            *ball_mov = HorizontalMovingStatus::default();
            robot_mov.locked = true;
            ball_mov.locked = true;
            // 移除惩罚区
            for entity in query_restricted_area.iter() {
                commands.entity(entity).despawn();
            }
            // 创建新的惩罚区
            let restrict_area_count = game_rng.usize(2..=4);
            let mut restrict_area_list = Vec::new();
            let material = materials.add(Color::RED.into());
            for _ in 0..restrict_area_count {
                let x = select(game_rng.f32(), -field_length / 4., field_length / 4.);
                let z = select(game_rng.f32(), -field_width / 4., field_width / 4.);
                let radius = select(
                    game_rng.f32(),
                    RESTRICTED_AREA_RADIUS_MIN,
                    RESTRICTED_AREA_RADIUS_MAX,
                );
                let data = RestrictedArea { x, z, radius };
                // 添加到列表
                restrict_area_list.push(data);
                // 创建惩罚区图形实体
                let mut circle_tran = Transform::from_xyz(x, 0.002, z);
                circle_tran.rotate_x(-PI / 2.);
                commands.spawn((
                    PbrBundle {
                        mesh: meshes.add(Mesh::from(shape::Circle::new(radius))),
                        material: material.clone(),
                        transform: circle_tran,
                        ..default()
                    },
                    data,
                ));
            }
            // 开启子进程
            let child_process_result =
                if statistics.process_exec_count >= game_arg.process_exec_count {
                    // 达到指定运行次数？
                    use std::io::{Error, ErrorKind};
                    Err(Error::new(ErrorKind::Other, "Count reached."))
                } else {
                    GameChildProcess::new(game_arg.link_program_path.as_str())
                };
            info!(
                "Ready with {}",
                if child_process_result.is_ok() {
                    format!(
                        "process, count: {}/{}",
                        statistics.process_exec_count + 1,
                        game_arg.process_exec_count
                    )
                } else {
                    "keyboard".to_string()
                }
            );
            match child_process_result {
                Ok(mut child_process) => {
                    // 发送起始数据
                    let data = SendInstData {
                        field_length,
                        field_width,
                        goal_area_width,
                        restrict_area_list,
                        robot_max_speed: ROBOT_MOVE_SPEED,
                        robot_max_rotate_speed: ROBOT_ROTATE_SPEED,
                        robot_radius: ROBOT_RADIUS,
                        ball_radius: BALL_RADIUS,
                    };
                    child_process.write_data(data);
                    // 添加资源列表
                    commands.insert_resource(child_process);
                    statistics.process_exec_count += 1;
                }
                Err(err) if !game_arg.link_program_path.is_empty() => {
                    warn!("Failed to start child_process! {}", err.to_string());
                }
                _ => {}
            }
        }
        GameStatus::Playing { .. } => {
            // 解锁，设置速度
            robot_mov.locked = false;
            ball_mov.locked = false;
            ball_mov.rotate_angle =
                BALL_MOVE_ANGLE_MIN + (BALL_MOVE_ANGLE_MAX - BALL_MOVE_ANGLE_MIN) * game_rng.f32();
            ball_mov.speed =
                BALL_MOVE_SPEED_MIN + (BALL_MOVE_SPEED_MAX - BALL_MOVE_SPEED_MIN) * game_rng.f32();
            info!("Playing");
        }
        GameStatus::End { .. } => {
            // 锁定位置
            robot_mov.locked = true;
            ball_mov.locked = true;
            // 统计
            *statistics += game_status;
            // 关闭子进程并移除资源
            // 已实现Drop
            commands.remove_resource::<GameChildProcess>();
            // 输出分数等信息
            let stat = statistics.as_ref();
            info!(
                "Round finished, Max:{} Total:{}\n{}",
                stat.max_score,
                stat.total_score,
                game_status.score().1.replace('\n', "")
            );
        }
    }
}

/// 摄像头视角跟随给定坐标
#[allow(clippy::type_complexity)]
fn camera_tracking(
    mut query_camera: Query<&mut Transform, (With<Camera3d>, Without<Robot>, Without<Ball>)>,
    query_status: Query<&GameStatus>,
    query_robot: Query<
        (&Transform, &HorizontalMovingStatus),
        (With<Robot>, Without<Ball>, Without<Camera3d>),
    >,
    query_ball: Query<&Transform, (With<Ball>, Without<Robot>, Without<Camera3d>)>,
) {
    let mut camera_tran = query_camera.single_mut();
    let (robot_tran, robot_mov) = query_robot.single();
    let ball_tran = query_ball.single();

    let tracking = CameraTrackingTranslation {
        translation: match query_status.single() {
            GameStatus::Playing { kick, .. } if *kick => ball_tran.translation,
            _ => robot_tran.translation,
        },
        rotate_angle: robot_mov.rotate_angle,
    };
    let horizontal_rotate_loc = (-tracking.get_rotate())
        .clamp_length(CAMERA_HORIZONTAL_DISTANCE, CAMERA_HORIZONTAL_DISTANCE);

    let camera_new_loc = tracking.translation + horizontal_rotate_loc + (CAMERA_HEIGHT * Vec3::Y);
    *camera_tran =
        Transform::from_translation(camera_new_loc).looking_at(tracking.translation, Vec3::Y);
}

/// 水平移动所有相关物体
fn move_horizontal(
    mut query_robot: Query<(&mut Transform, &HorizontalMovingStatus)>,
    time: Res<Time>,
) {
    for (mut tran, mov) in query_robot.iter_mut() {
        if mov.locked {
            continue;
        }
        tran.translation += mov.delta_distance(time.delta().as_secs_f32());
    }
}

/// 更新文本
fn update_tips(
    query_status: Query<&GameStatus>,
    mut query_tips_text: Query<&mut Text, (With<TipsText>, Without<StatisticsText>)>,
    mut query_statistics_text: Query<&mut Text, (With<StatisticsText>, Without<TipsText>)>,
    statistics: Res<Statistics>,
    game_arg: Res<GameArg>,
    game_child: Option<Res<GameChildProcess>>,
) {
    let game_status = query_status.single();
    let mut tips_text = query_tips_text.single_mut();
    tips_text.sections[0].value = game_status.tips();
    let mut statistics_text = query_statistics_text.single_mut();
    let value = &mut statistics_text.sections[0].value;
    *value = statistics.to_string();
    value.push('\n');
    value.push_str(format!("\nSeed: {}", game_arg.rand_seed).as_str());
    value.push_str(
        format!(
            "\nGameChildProcess Count: {}/{}",
            statistics.process_exec_count, game_arg.process_exec_count
        )
        .as_str(),
    );
    if game_child.is_some() {
        value.push_str("\nUsing GameChildProcess...");
    }
}

/// 准备状态下的子线程操作
fn ctrl_child_process_ready(
    mut commands: Commands,
    mut query_status: Query<(Entity, &mut GameStatus)>,
    game_child: Option<ResMut<GameChildProcess>>,
) {
    let Some(mut game_child) = game_child else {
        return;
    };
    let (entity, mut game_status) = query_status.single_mut();
    let GameStatus::Ready { start_time } = game_status.as_mut() else {
        return;
    };
    let elapsed_time = SystemTime::now()
        .duration_since(*start_time)
        .unwrap_or(Duration::MAX)
        .as_secs_f32();
    if elapsed_time < 0.5 {
        return;
    }
    // 接受数据包来确认是否已经准备好
    if !game_child.check_ready() {
        return;
    }
    // 准备好了，开始！
    *game_status = GameStatus::Playing {
        start_time: SystemTime::now(),
        caught: false,
        kick: false,
        punish_time: Duration::ZERO,
    };
    commands.entity(entity).insert(GameStatusChanged);
}

/// 游玩中状态下的键盘操作
#[allow(clippy::type_complexity)]
fn ctrl_child_process_playing(
    mut query_status: Query<&mut GameStatus>,
    mut query_robot: Query<(&Transform, &mut HorizontalMovingStatus), (With<Robot>, Without<Ball>)>,
    mut query_ball: Query<(&Transform, &mut HorizontalMovingStatus), (With<Ball>, Without<Robot>)>,
    game_child: Option<ResMut<GameChildProcess>>,
    time: Res<Time>,
) {
    let Some(mut game_child) = game_child else {
        return;
    };
    let mut query_status = query_status.single_mut();
    let GameStatus::Playing { caught, kick, .. } = query_status.as_mut() else {
        return;
    };
    let (robot_tran, mut robot_mov) = query_robot.single_mut();
    let robot_loc = robot_tran.translation;
    let (ball_tran, mut ball_mov) = query_ball.single_mut();
    let ball_loc = ball_tran.translation;

    // 发送数据
    let send_data = SendData {
        robot_x: robot_loc.x,
        robot_z: robot_loc.z,
        robot_speed: robot_mov.speed,
        robot_rotate_angle: robot_mov.rotate_angle,
        ball_x: ball_loc.x,
        ball_z: ball_loc.z,
        ball_speed: ball_mov.speed,
        ball_rotate_angle: ball_mov.rotate_angle,
        ball_caught: *caught,
    };
    game_child.write_data(send_data);

    // 接收返回数据
    let Ok(receive_data) = game_child.get_receive_data() else {
        return;
    };
    let ReceiveData {
        robot_speed,
        robot_rotate_speed,
        robot_kick,
    } = receive_data;

    // 处理返回数据
    let time_delta = time.delta().as_secs_f32();
    let robot_new_speed = range(robot_speed, -ROBOT_MOVE_SPEED, ROBOT_MOVE_SPEED);
    let robot_new_rotate_speed = range(robot_rotate_speed, -ROBOT_ROTATE_SPEED, ROBOT_ROTATE_SPEED);

    // 通过返回数据控制机器人行动
    // 移动
    robot_mov.speed = robot_new_speed;
    robot_mov.rotate_angle += robot_new_rotate_speed * time_delta;
    // 射门
    if robot_kick && *caught && (!*kick) {
        *kick = true;
        ball_mov.locked = false;
        ball_mov.rotate_angle = robot_mov.rotate_angle;
        ball_mov.speed = BALL_KICK_SPEED;
    }
}

/// 准备状态下的键盘操作
fn ctrl_keyboard_ready(
    mut commands: Commands,
    mut query_status: Query<(Entity, &mut GameStatus)>,
    game_child: Option<Res<GameChildProcess>>,
    keyboard: Res<Input<KeyCode>>,
) {
    if game_child.is_some() {
        return;
    }
    let (entity, mut game_status) = query_status.single_mut();
    let GameStatus::Ready { start_time } = game_status.as_mut() else {
        return;
    };
    if keyboard.just_pressed(KeyCode::Space) {
        let elapsed_time = SystemTime::now()
            .duration_since(*start_time)
            .unwrap_or(Duration::MAX)
            .as_secs_f32();
        if elapsed_time < 1.0 {
            return;
        }
        *game_status = GameStatus::Playing {
            start_time: SystemTime::now(),
            caught: false,
            kick: false,
            punish_time: Duration::ZERO,
        };
        commands.entity(entity).insert(GameStatusChanged);
    }
}

/// 游玩中状态下的键盘操作
/// 键盘移动机器人
#[allow(clippy::type_complexity)]
fn ctrl_keyboard_playing(
    mut query_status: Query<&mut GameStatus>,
    mut query_robot: Query<&mut HorizontalMovingStatus, (With<Robot>, Without<Ball>)>,
    mut query_ball: Query<&mut HorizontalMovingStatus, (With<Ball>, Without<Robot>)>,
    game_child: Option<Res<GameChildProcess>>,
    keyboard: Res<Input<KeyCode>>,
    time: Res<Time>,
) {
    if game_child.is_some() {
        return;
    }
    let mut query_status = query_status.single_mut();
    let GameStatus::Playing { caught, kick, .. } = query_status.as_mut() else {
        return;
    };
    let mut robot_mov = query_robot.single_mut();

    // 移动
    let time_delta = time.delta().as_secs_f32();
    for keycode in keyboard.get_pressed() {
        match keycode {
            KeyCode::W => robot_mov.speed = ROBOT_MOVE_SPEED,
            KeyCode::A => robot_mov.rotate_angle += ROBOT_ROTATE_SPEED * time_delta,
            KeyCode::S => robot_mov.speed = -ROBOT_MOVE_SPEED,
            KeyCode::D => robot_mov.rotate_angle -= ROBOT_ROTATE_SPEED * time_delta,
            _ => {}
        }
    }
    for keycode in keyboard.get_just_released() {
        match keycode {
            KeyCode::W | KeyCode::S => robot_mov.speed = 0.0,
            _ => {}
        }
    }
    // 射门
    if keyboard.just_pressed(KeyCode::Space) {
        if (!*caught) || *kick {
            return;
        }
        let Ok(mut ball_mov) = query_ball.get_single_mut() else {
            return;
        };
        *kick = true;
        ball_mov.locked = false;
        ball_mov.rotate_angle = robot_mov.rotate_angle;
        ball_mov.speed = BALL_KICK_SPEED;
    }
}

/// 游玩结束状态下的键盘操作
/// 也包括带子进程的操作
fn ctrl_process_end(
    mut commands: Commands,
    mut query_status: Query<(Entity, &mut GameStatus)>,
    keyboard: Res<Input<KeyCode>>,
    game_arg: Res<GameArg>,
) {
    let (entity, mut game_status) = query_status.single_mut();
    let GameStatus::End {
        end_time,
        has_child_process,
        ..
    } = game_status.as_ref()
    else {
        return;
    };
    // 键盘按重启
    let keyboard_pressed = keyboard.just_pressed(KeyCode::Space);
    // 有子线程的情况下自动重启
    let elapsed_time = SystemTime::now()
        .duration_since(*end_time)
        .unwrap_or_default()
        .as_secs_f32();
    let child_process_auto_start = *has_child_process && elapsed_time > game_arg.end_waiting_time;
    // 重启
    if keyboard_pressed || child_process_auto_start {
        *game_status = GameStatus::Ready {
            start_time: SystemTime::now(),
        };
        commands.entity(entity).insert(GameStatusChanged);
    }
}

/// 在球被捕获时球的移动
#[allow(clippy::type_complexity)]
fn move_ball_caught(
    query_status: Query<&GameStatus>,
    mut query_ball: Query<&mut Transform, (With<Ball>, Without<Robot>)>,
    query_robot: Query<(&Transform, &HorizontalMovingStatus), (With<Robot>, Without<Ball>)>,
) {
    let GameStatus::Playing { caught, kick, .. } = query_status.single() else {
        return;
    };
    if (!*caught) || *kick {
        return;
    }
    // 接到球了，把球移到前面
    let mut ball_tran = query_ball.single_mut();
    let (robot_tran, robot_mov) = query_robot.single();
    let mut ball_loc_delta = robot_mov
        .angle_vec3()
        .clamp_length_max(ROBOT_RADIUS + BALL_RADIUS);
    ball_loc_delta.y = ball_tran.translation.y;
    ball_tran.translation = robot_tran.translation + ball_loc_delta;
    ball_tran.translation.y = ball_loc_delta.y;
}

/// 抓球
#[allow(clippy::type_complexity)]
fn catch_ball(
    mut query_status: Query<&mut GameStatus>,
    mut query_ball: Query<(&Transform, &mut HorizontalMovingStatus), With<Ball>>,
    query_robot: Query<(&Robot, &Transform, &HorizontalMovingStatus), Without<Ball>>,
) {
    let mut query_status = query_status.single_mut();
    let GameStatus::Playing { caught, kick, .. } = query_status.as_mut() else {
        return;
    };
    if *caught {
        return;
    }
    let (ball_tran, mut ball_mov) = query_ball.single_mut();
    let (robot_status, robot_tran, robot_mov) = query_robot.single();
    let mut caught_loc = robot_tran.translation
        + robot_mov
            .angle_vec3()
            .clamp_length(ROBOT_RADIUS + BALL_RADIUS, ROBOT_RADIUS + BALL_RADIUS);
    caught_loc.y = ball_tran.translation.y;
    // 还在冷却，不抓了
    let duration_from_last_kick = SystemTime::now()
        .duration_since(robot_status.last_kick_time)
        .unwrap_or_default()
        .as_secs_f32();
    if duration_from_last_kick < ROBOT_SHOT_RELAXING_TIME {
        return;
    }
    // 射门了，不抓了
    if *kick {
        return;
    }
    // 没抓住
    if (ball_tran.translation - caught_loc).length_squared() > CAUGHT_DISTANCE * CAUGHT_DISTANCE {
        return;
    }
    // 抓住了
    *caught = true;
    ball_mov.locked = true;
}

/// 检查球是否出界
fn ball_out(
    mut commands: Commands,
    mut query_status: Query<(Entity, &mut GameStatus)>,
    query_ball: Query<&Transform, With<Ball>>,
    field_data: Res<FieldData>,
    game_child: Option<Res<GameChildProcess>>,
) {
    // 是否游戏中
    let (entity, mut game_status) = query_status.single_mut();
    let GameStatus::Playing {
        caught,
        kick,
        start_time,
        punish_time,
        ..
    } = game_status.to_owned()
    else {
        return;
    };
    // 检查是否出界
    let FieldData {
        field_length,
        field_width,
        line_width,
        goal_area_width,
        ..
    } = field_data.to_owned();
    let ball_tran = query_ball.single();
    let ball_loc = ball_tran.translation;
    if in_radius(ball_loc.x, (field_length / 2.) + line_width)
        && in_radius(ball_loc.z, (field_width / 2.) + line_width)
    {
        return;
    }
    // 出界了，检查是否进球
    let goal = kick && in_radius(ball_loc.z, goal_area_width / 2.) && ball_loc.x > 0.0;
    *game_status = GameStatus::End {
        start_time,
        end_time: SystemTime::now(),
        caught,
        kick,
        goal,
        has_child_process: game_child.is_some(),
        punish_time,
    };
    commands.entity(entity).insert(GameStatusChanged);
}

/// 检查限制区
fn check_restricted_area(
    mut query_status: Query<&mut GameStatus>,
    query_robot: Query<&Transform, (With<Robot>, Without<Ball>)>,
    query_ball: Query<&Transform, (Without<Robot>, With<Ball>)>,
    query_restricted_area: Query<(&Transform, &RestrictedArea)>,
    time: Res<Time>,
) {
    // 是否游戏中
    let mut game_status = query_status.single_mut();
    let GameStatus::Playing { punish_time, .. } = game_status.as_mut() else {
        return;
    };
    let robot_tran = query_robot.single();
    let ball_tran = query_ball.single();
    let mut robot_in = false;
    let mut ball_in = false;
    for (area_tran, area_info) in query_restricted_area.iter() {
        let mut robot_delta = area_tran.translation - robot_tran.translation;
        robot_delta.y = 0.0;
        if robot_delta.length() < ROBOT_RADIUS + area_info.radius {
            robot_in = true;
        }
        let mut ball_delta = area_tran.translation - ball_tran.translation;
        ball_delta.y = 0.0;
        if ball_delta.length() < BALL_RADIUS + area_info.radius {
            ball_in = true;
        }
    }
    if robot_in {
        *punish_time += time.delta();
    }
    if ball_in {
        *punish_time += time.delta();
    }
}

/*
 * 性能测试部分
 */
#[derive(Resource, Clone, Copy, Default)]
#[allow(unused)]
struct PerfCount {
    count: usize,
    time: Duration,
}

#[allow(unused)]
fn update_count_system(mut perf_count: ResMut<PerfCount>, time: Res<Time>) {
    perf_count.count += 1;
    perf_count.time += time.delta();
    if perf_count.count % 100 == 0 {
        info!(
            "Count: {} Time: {:.3} CPS: {:.3}",
            perf_count.count,
            perf_count.time.as_secs_f32(),
            (perf_count.count as f32) / perf_count.time.as_secs_f32()
        );
    }
}

/*
 * 附带函数
 */

fn in_radius<T>(arg: T, radius: T) -> bool
where
    T: Copy + PartialOrd + Neg<Output = T>,
{
    (-radius) <= arg && arg <= radius
}

fn range<T>(arg: T, min: T, max: T) -> T
where
    T: Copy + PartialOrd + Neg<Output = T>,
{
    if arg < min {
        min
    } else if arg > max {
        max
    } else {
        arg
    }
}

fn select(ratio: f32, min: f32, max: f32) -> f32 {
    assert!((0.0..=1.0).contains(&ratio));
    min + ratio * (max - min)
}
